package common.fattree;

import common.*;
import common.correspondence.Message;
import common.proto.out.CountMessage;

import java.io.File;
import java.util.*;

public class FattreeDevice extends Device {
    public static String LOG_OUTPUT_DIR_PATH = "time/fattree/";

    public static final String corePrefix = "ssw";
    public static final String aggregationPrefix = "fsw";
    public static final String edgePrefix = "rsw";
    public static final String hostPrefix = "h";
    enum DeviceType{
        core, aggregation, edge, h
    }

    DeviceType deviceType;
//    Map<String, DebugPort> portMap;
    int ipPredicate;
    Node[] nodes;
    Utility.Callback callback;
    int nodeIndex;

    public FattreeDevice(String configFileName, String fibFileName, int nodeIndex, Utility.Callback callback){
        super();
        nodes = new Node[2];
        this.callback = callback;
//        portMap = new HashMap<>();
        this.nodeIndex = nodeIndex;
        readConfigFile(configFileName);
        if(deviceType != DeviceType.h)
            readForwardFile(fibFileName);
        createNode(nodeIndex);

        File file = new File(LOG_OUTPUT_DIR_PATH);
        file.mkdirs();
        lr = new LogRecorder(LOG_OUTPUT_DIR_PATH + name + "_time.csv");
//        System.out.println(Arrays.toString(nodes));
//        System.out.println(ports.values());
    }

    protected void readConfigFile(String configName){
        if(!fileOpen(configName)){

            System.exit(0);
        }

        String line = readFileLine();
        String[] data = split(line,3);

        name = data[0];
        ip = data[1];
        deviceType = DeviceType.valueOf(data[2]);

        while ((line = readFileLine()) != null) { //读取出来的数据存放在数组str中
            data = split(line, 2);
            if(data[0].equals("link")){
                data = split(data[1],5);
                addLink(data[0], data[1], data[2], data[3], data[4]);
            } else if(data[0].equals("ip")){
                data = split(data[1], 2);
                setDeviceIp(Long.parseLong(data[0]), Integer.parseInt(data[1]));
            }
        }
        fileClose();
    }

    @Override
    protected void getChangeToUpdateCIB(List<Change> changes, long t) {
        for (Node n : nodes) {
            String id = Utility.getRandomString();
            addRecord(id, n.index, "* cib change start", t);
            long ts = System.nanoTime();
//            addRecord(id, n.index, "lec change", ts-t);
            n.getChangeToUpdateCIB(changes, id);
        }
    }
    protected void addLink(String typeString, String name, String ip, String recPort, String sendToPort){

        DeviceType dt = DeviceType.valueOf(typeString);
        deviceTable.put(name, ip);
        DebugPort p = new DebugPort(name, this, dt);
        ports.put(name, p);
    }

    void send(String dst, Map<String, PortInterface> map, Message msg){
        PortInterface p = map.get(dst);
        if(p != null)
            p.send(msg);
    }

    void createHostNode(int nodeIndex){
        Node node;
        node = new HostNode(this, nodeIndex, ipPredicate);
        for(PortInterface d:ports.values()){
            DebugPort dp = (DebugPort) d;
            if(dp.linkType == DeviceType.edge){
                if(nodeIndex == 0)
                    node.addPrev(new NodePointer(dp.name, 0, 0));
                else
                    node.addNext(new NodePointer(dp.name, 1, 0));
            }
        }
        nodes[nodeIndex] = node;
    }

    void createEdgeNode(int nodeIndex){
        Node node;
        node = new StaticForwardingNode(this, nodeIndex);
        for(PortInterface d:ports.values()){
            DebugPort dp = (DebugPort) d;
            if(dp.linkType == DeviceType.h){
                if(nodeIndex == 0)
                    node.addNext(new NodePointer(dp.name, 0, 0));
                else
                    node.addPrev(new NodePointer(dp.name, 1, 0));
            }else if(dp.linkType == DeviceType.aggregation){
                if(nodeIndex == 0)
                    node.addPrev(new NodePointer(dp.name, 0, 0));
                else
                    node.addNext(new NodePointer(dp.name, 1, 0));
            }
        }
        nodes[nodeIndex] = node;

    }

    void createAggregationNode(int nodeIndex){
        Node node;
        if(nodeIndex == 0)
            node = new DynamicForwardingNode(this, nodeIndex);
        else
            node = new StaticForwardingNode(this, nodeIndex);
        Map<String, NodePointer> nodeMap = new HashMap<>();
        List<NodePointer> edgeList = new ArrayList<>();
        List<NodePointer> coreList = new ArrayList<>();
        for(PortInterface d:ports.values()){
            DebugPort dp = (DebugPort) d;
            if(dp.linkType == DeviceType.edge){
                NodePointer np = new NodePointer(dp.name, 1, 0);
                nodeMap.put(dp.name, np);
                edgeList.add(np);
            } else if(dp.linkType == DeviceType.core){
                NodePointer np = new NodePointer(dp.name, 0, 0);
                nodeMap.put(dp.name, np);
                coreList.add(np);
            }
        }
        Map<String, List<NodePointer>> forwardMap = new HashMap<>();

        for(PortInterface d:ports.values()){
            DebugPort dp = (DebugPort) d;
            if(nodeIndex==0 && dp.linkType == DeviceType.edge){
                NodePointer np = nodeMap.get(dp.name);
                List<NodePointer> nl = new ArrayList<>(coreList);
                nl.addAll(edgeList);
                nl.remove(np);
                forwardMap.put(dp.name, nl);
            }else if(nodeIndex == 1){
                if(dp.linkType == DeviceType.edge)
                    node.addPrev(nodeMap.get(dp.name));
                else if (dp.linkType == DeviceType.core)
                    node.addNext(nodeMap.get(dp.name));
            }
        }
        if(nodeIndex==0) {
            DynamicForwardingNode n = (DynamicForwardingNode) node;
            n.setForwardTable(forwardMap);
        }
        nodes[nodeIndex] = node;
    }

    void createCoreNode(){
        DynamicForwardingNode node = new DynamicForwardingNode(this, 0);
        List<NodePointer> lp = new ArrayList<>();
        Map<String, NodePointer> nodeMap = new HashMap<>();
        Map<String, List<NodePointer>> forwardTable = new HashMap<>();
        for(PortInterface d:ports.values()){
            DebugPort dp = (DebugPort) d;
            if(dp.linkType == DeviceType.aggregation){
                NodePointer np = new NodePointer(dp.name, 1, 0);
                lp.add(np);
                nodeMap.put(dp.name, np);
            }
        }
        for(PortInterface d:ports.values()){
            DebugPort dp = (DebugPort) d;
            if(dp.linkType == DeviceType.aggregation) {
                NodePointer np = nodeMap.get(dp.name);
                List<NodePointer> nl = new ArrayList<>(lp);
                nl.remove(np);
                forwardTable.put(dp.name, nl);
            }
        }
        node.setForwardTable(forwardTable);
        nodes[0] = node;
    }

    void createNode(int nodeIndex){
        if (deviceType == DeviceType.core) createCoreNode();
        else if (deviceType == DeviceType.aggregation) createAggregationNode(nodeIndex);
        else if (deviceType == DeviceType.edge) createEdgeNode(nodeIndex);
        else if (deviceType == DeviceType.h) createHostNode(nodeIndex);
    }

    void setDeviceIp(long ip, int prefix){
        this.ipPredicate = bddEngine.encodeDstIPPrefix(ip, prefix);
    }

    @Override
    public void run() {
        for(Node node:nodes){
            if(node != null){
                node.start();
            }
        }

        if(deviceType == DeviceType.h && nodeIndex == 0){
            nodes[0].startCount();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                return;
            }
        }else {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                return;
            }
            for(PortInterface d:ports.values()){
                DebugPort dp = (DebugPort) d;
                dp.start();
            }
            untilAllPortAlready();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                return;
            }
        }
        for(PortInterface d:ports.values()){
            DebugPort dp = (DebugPort) d;
            dp.close();
        }
        for(Node node:nodes){
            if(node != null)
                node.close();
        }
        this.lr.writeAll();
        callback.execute(name);
    }

    void untilAllPortAlready(){
        for(PortInterface d:ports.values()){
            DebugPort dp = (DebugPort) d;
            if(!dp.already){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }


    public void sendCount(Iterable<NodePointer> nps, int index, CIBIO cibOut, String id){
        long t1 = System.nanoTime();
        List<Integer> bddList = new LinkedList<>();
        for(Announcement an:cibOut.announcements){
            bddList.add(an.predicate);
        }
        if(cibOut.packetSpace!=0) bddList.add(cibOut.packetSpace);

        CountMessage.Cib.Builder cibBuilder = CountMessage.Cib.newBuilder();
        cibBuilder.addAllWithdraw(cibOut.withdraw);
        for(Announcement an:cibOut.announcements){
            CountMessage.Announcement.Builder aBuilder = CountMessage.Announcement.newBuilder();
            aBuilder.addAllCount(an.count);
            aBuilder.setPredicate(an.predicate);
            aBuilder.setId(an.id);
            cibBuilder.addAnnouncements(aBuilder.build());
        }
        CountMessage.CountResult.Builder builder = CountMessage.CountResult.newBuilder();
        builder.setId(id);
        builder.setSourceNodeIndex(index);
        builder.setCib(cibBuilder.build());
        builder.setDestination(cibOut.dst);
        builder.setPacketSpace(cibOut.packetSpace);
        long tt1 = System.nanoTime();
        bt.bddsToArray(bddList, builder);
        long tt2 = System.nanoTime();

//        builder.addAllNodeTree(bddNodeTree);
        builder.setDebug(false);
        long t2 = System.nanoTime();
//        System.out.println(nps);

        for(NodePointer np:nps){
            builder.setRequirementIndex(np.reqIndex);
            builder.setDestinationNodeIndex(np.index);
            DebugPort dp = (DebugPort) ports.get(np.name);
//            PortInterface
            dp.send(new Message(Message.MessageType.Count.ordinal(), builder.build()));

        }
        addRecord(id, index, "*msg size", builder.build().getSerializedSize());
        long t3 = System.nanoTime();
//        addRecord(id, index, "cib num", cibOut.announcements.size());
//        addRecord(id, index, "count save", t2-t1);
//        addRecord(id, index, "serialization", t3-t2);
    }

    @Override
    public void receiveCount(CountMessage.CountResult res, String src, long time) {
        long t1 = System.nanoTime();

        CIBIO cibio = new CIBIO();
        cibio.withdraw = res.getCib().getWithdrawList();
        cibio.announcements = new Vector<>();
        cibio.dst = res.getDestination();
        cibio.packetSpace = res.getPacketSpace();
        CountMessage.Cib cib = res.getCib();
        for(CountMessage.Announcement an: cib.getAnnouncementsList()){
            cibio.announcements.add(new Announcement(an.getId(), an.getPredicate(), new Vector<>(an.getCountList())));
        }
        NodePointer np = new NodePointer(src, res.getSourceNodeIndex(), res.getRequirementIndex());
        String id =res.getId();
        long t2, t3;
        t2 = System.nanoTime();
        bt.cibIOPredicateParse(res.getNodeTreeList(), cibio);
        t3 = System.nanoTime();
        getNodeList(res.getRequirementIndex())[res.getDestinationNodeIndex()].receiveCount(cibio, np, id);
        addRecord(id, res.getDestinationNodeIndex(), "*count receive", time);
//        addRecord(id, res.getDestinationNodeIndex(), "count load", t2-t1);
//        addRecord(id, res.getDestinationNodeIndex(), "bdd load", t3-t2);
//        }

    }
    @Override
    protected Node[] getNodeList(int reqIndex) {
        return nodes;
    }
}
